# -*- coding: utf-8 -*-
"""
# @file webserver.py
# @author Makaka
# @date 2023-11-08
# @brief Fastapi定义网络接口,并通过此接受来自客户端的数据;获得数据后存入队列,并通过socket取出该数据传给c++
"""
from fastapi import FastAPI
from fastapi_utils.tasks import repeat_every
import uvicorn
import threading
import time
import socket
import sys
import queue
import logging
import yaml

def get_config_value(config_file_path, variable_name):
    ##############################
    # @description 读取yaml文件中的配置信息
    # @return variable_name对应的值
    ##############################
    with open(config_file_path, 'r', encoding='utf-8') as stream:
        try:
            config = yaml.safe_load(stream)
            return config[variable_name]
        except yaml.YAMLError as exc:
            logger.info(exc)
            return None

def setup_logger(log_output_file, log_level=logging.INFO):
    ##############################
    # @description 设置log库，将日志同时输出到文件和控制台(删除所有注释行可只输出到文件)
    ##############################
    logger = logging.getLogger(__name__)
    logger.setLevel(log_level)
    fh = logging.FileHandler(log_output_file)
    ch = logging.StreamHandler()  # 创建一个StreamHandler
    formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
    fh.setFormatter(formatter)
    ch.setFormatter(formatter)  # 设置StreamHandler使用相同的日志格式
    logger.addHandler(fh)
    logger.addHandler(ch)  # 将StreamHandler添加到logger
    return logger

MAX_RETRIES = 5  # 设置最大重试连接地面端scsn次数，每次5秒钟
MAXQUEUESIZE = 20
ADDRTARGET_IP = get_config_value('config.yaml', 'ADDRTARGET_IP')
ADDRTARGET_PORT = get_config_value('config.yaml', 'ADDRTARGET_PORT')
ADDRTARGET = (ADDRTARGET_IP, ADDRTARGET_PORT) # 发送目标IP与端口信息
app = FastAPI()
shareQueue = queue.Queue(maxsize=MAXQUEUESIZE)
queueLock = threading.Lock()
logger = setup_logger('fastapilog.log')

@app.get('/') 
def helloWorld():
    return "hello world"

@app.post('/cmd/')
def getName(info: dict):
    ##############################
    # @description 将请求来的数据存入队列,info为查询参数,无需在路径中定义直接传过来解析即可
    # @param 
    # @return 
    ##############################
    global shareQueue
    queueLock.acquire()
    try:
        shareQueue.put(info)
    except Exception as ex:
        logger.info("格式错误" + str(ex))
    queueLock.release()
    print(info)
    return {
            "code": 200, #反馈是否能接收到命令
            "message":"success"
        }

@app.post('/task/')
def getName(info: dict):
    ##############################
    # @description 将请求来的数据存入队列,info为查询参数,无需在路径中定义直接传过来解析即可
    # @param 
    # @return 
    ##############################
    global shareQueue
    queueLock.acquire()
    try:
        shareQueue.put(info)
    except Exception as ex:
        logger.info("格式错误" + str(ex))
    queueLock.release()
    print(info)
    return {
            "code": 200, #反馈是否能接收到命令
            "message":"success"
        }

"""
放在该修饰器下可以解决Fastapi的路由函数与普通函数全局队列不共享的问题
"""
@app.on_event("startup")
@repeat_every(seconds=1, logger=logger, wait_first=True)
def periodic():
    ##############################
    # @description 连接socket,设定get延时,若队列中存在数据则传给c++,没有则跳过继续等
    # @param seconds  每隔1秒执行一次
    # @return None
    ##############################
    global shareQueue, queueLock

    for i in range(MAX_RETRIES):
        try:
            logger.info("准备连接至地面端scsn(妙算)!")
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect(ADDRTARGET)
            logger.info("连接成功!")
            break  # 如果连接成功，就跳出循环
        except Exception as ex:
            logger.info(f"连接失败，错误信息：{ex}")
            if i < MAX_RETRIES - 1:  # 如果不是最后一次尝试，就等待一会儿再重试
                logger.info("5秒钟后尝试重新连接...")
                time.sleep(5)  # 等待1秒
            else:  # 如果是最后一次尝试，就抛出异常
                logger.info("尝试多次连接失败，程序退出。")
                sys.exit(1)

    logger.info("连接成功!")
    while True:
        logger.info("开始运行")
        try:
            sendData = str(shareQueue.get(3))
            s.send(sendData.encode('utf-8'))  # 将要传输的数据编码发送，如果是字符数据就必须要编码发送
            logger.info(f"发送数据{sendData}")
        except queue.Empty:
            logger.info('队列为空,get失败')
        except Exception as ex:
            logger.info(f"出现如下异常{ex}")
            time.sleep(1)

if __name__ == "__main__":
    uvicorn.run("webserver:app", host="0.0.0.0", port=8000, workers=1)
